package uk.ac.strath.cis.metric_spaces.framework;

import java.io.File;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.ac.strath.cis.metric_spaces.util.FileUtilities;

/**
 * This class provides a Facade to the Distance Metric Framework. It provides
 * utility methods for easily performing calculations across a range of
 * serialised types, including parsing and performing distance calculations.
 * 
 * @author Robert
 * 
 * @param <E>
 */
public class DistanceMetricFacade<E> {

    private Parser<E> parser;
    private Distance<E> calculator;

    public void setParser(Parser<E> parser) {
        this.parser = parser;
    }

    public void setCalculator(Distance<E> calculator) {
        this.calculator = calculator;
    }

    /**
     * Calculates the distance between two elements, specified by their file names
     * 
     * @param filename1
     * @param filename2
     * @return
     * @throws Exception
     */
    public double distance(String filename1, String filename2)
            throws Exception {
        return distance(new File(filename1), new File(filename2));
    }

    /**
     * @param file1
     * @param file2
     * @return
     * @throws Exception
     */
    public double distance(File file1, File file2) throws Exception {
        E t1 = parse(file1);
        E t2 = parse(file2);
        double distance = calculator.distance(t1, t2);
        return distance;
    }

    /**
     * @param t1
     * @param t2
     * @return
     */
    public double distance(E t1, E t2) {
        return calculator.distance(t1, t2);
    }

    /**
     * @param t
     * @param filename
     * @return
     * @throws Exception
     */
    public double distance(E t, String filename) throws Exception {
        E t2 = parse(filename);
        double distance = calculator.distance(t, t2);
        return distance;
    }

    /**
     * @param filename
     * @return
     * @throws Exception
     */
    private E parse(String filename) throws Exception {
        return parse(new File(filename));
    }

    /**
     * @param file
     * @return
     * @throws Exception
     */
    private E parse(File file) throws Exception {
        Reader r = FileUtilities.newBufferedReader(file);
        return parser.parse(r);
    }

    /**
     * @param t1
     * @param ts
     * @return
     */
    public double[] distanceOneToMany(E t1, List<E> ts) {
        double[] matrix = new double[ts.size() + 1];
        matrix[0] = 0.0;
        for (int i = 0; i < ts.size(); i++) {
            matrix[i + 1] = distance(t1, ts.get(i));
        }
        return matrix;
    }

    /**
     * @param t1
     * @param ts
     * @return
     * @throws Exception
     */
    public double[] distanceOneToMany(String t1, String... ts)
            throws Exception {
        double[] matrix = new double[ts.length + 1];
        matrix[0] = 0.0;
        for (int i = 0; i < ts.length; i++) {
            matrix[i + 1] = distance(t1, ts[i]);
        }
        return matrix;
    }

    /**
     * @param filename1
     * @param filenames
     * @return
     * @throws Exception
     */
    public Map<String, Double> distanceOneToManyAsMap(
            String filename1, String... filenames) throws Exception {

        Map<String, Double> distances = new HashMap<String, Double>();
        for (String filename : filenames) {
            String name1 = new File(filename1).getName();
            String name2 = new File(filename).getName();
            String names = String.format("%1$-16s & %2$-16s", name1.substring(
                    0, name1.lastIndexOf(".")), name2.substring(0, name2
                    .lastIndexOf(".")));
            distances.put(names, distance(filename1, filename));
        }
        return distances;
    }

    /**
     * @param t
     * @param filenames
     * @return
     * @throws Exception
     */
    public Map<String, Double> distanceOneToManyAsMap(E t,
            String... filenames) throws Exception {

        Map<String, Double> distances = new HashMap<String, Double>();
        for (String filename : filenames) {
            String name = new File(filename).getName();
            String names = String.format("%1$-16s & %2$-16s", "Tree", name
                    .substring(
                            (name.contains("/") ? name.lastIndexOf("/") : 0),
                            name.lastIndexOf(".")));
            distances.put(names, distance(t, filename));
        }
        return distances;
    }

    /**
     * @param filenames
     * @return
     * @throws Exception
     */
    public double[][] distanceManyToMany(String... filenames)
            throws Exception {
        List<E> trees = new ArrayList<E>();
        for (int i = 0; i < filenames.length; i++) {
            trees.add(parse(filenames[i]));
            System.out.println(trees.get(i));
        }
        return distanceManyToManyFromElement(trees);
    }

    /**
     * @param filenames
     * @return
     * @throws Exception
     */
    public double[][] distanceManyToMany(List<String> filenames)
            throws Exception {
        return distanceManyToMany(filenames
                .toArray(new String[filenames.size()]));
    }

    /**
     * @param ts
     * @return
     */
    public double[][] distanceManyToManyFromElement(List<E> ts) {
        double[][] matrix = new double[ts.size()][ts.size()];
        for (int i = 0; i < ts.size(); i++) {
            matrix[i] = distanceOneToMany(ts.get(i),
            // subArray(i + 1, trees.get(i), trees)
                    ts.subList(i + 1, ts.size()));
        }
        return matrix;
    }

    /**
     * @param filenames
     * @return
     * @throws Exception
     */
    public Map<String, Double> distanceManyToManyAsMap(
            String... filenames) throws Exception {
        Map<String, Double> list = new HashMap<String, Double>();

        List<String> files = Arrays.asList(filenames);
        List<String> fs = null;
        for (int i = 0; i < filenames.length; i++) {
            fs = files.subList(i + 1, files.size());
            list.putAll(distanceOneToManyAsMap(filenames[i], fs
                    .toArray(new String[fs.size()])));
        }
        return list;
    }
}
